# https://www.burqee.com/article/12/#GIF-%E7%9A%84-LZW-%E5%8E%8B%E7%BC%A9
# https://www.burqee.com/article/13/


class CodeToBinary:
    def __init__(self):
        self.total_binary_string = ""
        self.bin_list = []
    
    def add_output_code(self, output_code, size:int, is_last=False):
        origin_8bit_bin = ''.join(format(output_code, '08b'))
        self.total_binary_string = origin_8bit_bin[-size:] + self.total_binary_string
        self.get_bin_list()

        if is_last:
            self.total_binary_string = self.total_binary_string.zfill(8)
            self.get_bin_list()

    def get_bin_list(self):
        # 达到8位则生成一个字节
        while len(self.total_binary_string) >= 8:
            binary_str = self.total_binary_string[-8:]
            decimal = int(binary_str, 2)
            self.bin_list.append(decimal)
            self.total_binary_string = self.total_binary_string[0:-8]

        
 
class CodeToByteList:
    def __init__(self, file_path):
        self.file_path = file_path

    def load_code(self):
        with open(self.file_path, 'r', encoding='utf8') as f:
            self.code = f.read()    
        
        lines = self.code.split('\n')
        self.code_list = []
        for line in lines:
            params = line.split(",")
            for param in params:
                if param == "":
                    continue
                self.code_list.append(param.strip())    
        return self

    def lzw_compress(self, uncompressed, color_size, MiniCodeSize):
        # 创建字典，初始化为256个ASCII字符
        dictionary = {f"{i}":i for i in range(0,color_size)}
        dictionary["clear"] = len(dictionary)
        dictionary["end"] = len(dictionary)               
        
        data = uncompressed.copy()

        index_cached = data[0] # 取输入流中的第一个数据存入索引缓存, 即输入流列中带括号的数字
        size = MiniCodeSize + 1
        idx = 1
        output_code = dictionary["clear"]

        codeToBinary = CodeToBinary()
        codeToBinary.add_output_code(output_code, size)
        result = [output_code] # 初始化 
        while idx < len(data):
            k = data[idx] # 先将索引缓存后面的值读入 K
            combination = index_cached + k
            if combination in dictionary:
                index_cached = combination
            else:                
                # 用于将码流生成字节流    
                output_code = dictionary[index_cached]
                codeToBinary.add_output_code(output_code, size)
                result.append(output_code)  #然后将 {CODE} 添加到输出流                              
                
                if 2 ** size - 1 == max(dictionary.values()):
                    size = size + 1
                dictionary[combination] = len(dictionary) # 向颜色索引表中添加 #6 - (1,1) 这条数据               
                
                index_cached = k

            idx = idx + 1
        
        if index_cached != '':
            output_code = dictionary[index_cached]
            codeToBinary.add_output_code(output_code, size)
            result.append(output_code)

        output_code = dictionary["end"]
        codeToBinary.add_output_code(output_code, size, is_last=True)
        result.append(output_code) # 当处理完所有的数据后不要忘记在最后加上一个信息终止码，在这里的 #5
        print("-----------------dictionary-----------------")
        print(dictionary)
        return result, codeToBinary.bin_list
    
    def lzw_decompress(self, compressed, color_size):
        # 创建字典，初始化为256个ASCII字符
        dictionary = {i:f"{i}" for i in range(0,color_size)}
        dictionary[len(dictionary)] = "clear"
        dictionary[len(dictionary)] = "end"
        
        result = [] # 初始化 

        data = compressed.copy()

        idx = 1
        code = data[idx]
        result.append(dictionary[code])
        idx = idx + 1
        while idx < len(data):
            code = data[idx] # 先将索引缓存后面的值读入 K
            code_1 = data[idx - 1]
            if code in dictionary:
                fullCode = dictionary[code]
                if fullCode != "end":                     
                    k = fullCode[0] # {CODE} 的第一元素赋值给 K 
                    result.append(fullCode) # 将 {CODE} 添加到输出流                    
                    dictionary[len(dictionary)] = dictionary[code_1] + k
                elif fullCode == "clear": # 读到清空码，如 #4，此时就必须将之前生成的颜色索引表清除，重置为一开始的状态，然后再处理
                    dictionary = {i:f"{i}" for i in range(0,color_size)}
                    dictionary[len(dictionary)] = "clear"
                    dictionary[len(dictionary)] = "end"
            else:
                k = f"{code_1}"[0] # K = {CODE - 1} 的第一个元素
                dictionary[len(dictionary)] = f"{code_1}{k}" # 向颜色索引表中添加 #6 - (1,1) 这条数据
                result.append(f"{code_1}{k}") 
            idx = idx + 1
        
        return "".join(result)
    

    def get_key_by_value(self,d, value):
        for key, val in d.items():
            if val == value:
                return key
        return None  # 如果没有找到，返回 None

    def lzw_bin_to_code(self, compressed, color_size, MiniCodeSize = 2):
        # 创建字典，初始化为256个ASCII字符
        dictionary = {i:f"{i}" for i in range(0,color_size)}
        dictionary[len(dictionary)] = "clear"
        dictionary[len(dictionary)] = "end" 
        
        result = [] # 初始化 

        data = compressed.copy()

        size = MiniCodeSize + 1

        binary_str = ''

        d_code = ''
        d_code_1 = ''

        for b in data:
            binary_str = ''.join(format(b, '08b')) + binary_str
            while (len(binary_str) >= size):
                cut_str = binary_str[-size:] # 取后面的size位
                binary_str = binary_str[0:len(binary_str)-size]
                code = int(cut_str, 2)
                k = f"{code}"                
                result.append(code)  
                # -------------通过解压过程生成颜色索引表，求得size-----------------start
                d_code = f"{code}"
                fullCode = ''
                
                
                if 2 ** size - 1 == max(dictionary.keys()):
                    size = size + 1

                if d_code_1 != '':
                    if d_code in dictionary:
                        fullCode = dictionary[d_code]
                        if fullCode != "end" and fullCode != "clear":                     
                            k = fullCode[0] # {CODE} 的第一元素赋值给 K    
                            dictionary[len(dictionary)] = dictionary[int(d_code_1)] + dictionary[int(k)]
                        elif fullCode == "clear": # 读到清空码，如 #4，此时就必须将之前生成的颜色索引表清除，重置为一开始的状态，然后再处理
                            dictionary = {i:f"{i}" for i in range(0,color_size)}
                            dictionary[len(dictionary)] = "clear"
                            dictionary[len(dictionary)] = "end" 
                    else:
                        k = d_code_1[0] # K = {CODE - 1} 的第一个元素
                        dictionary[len(dictionary)] = d_code_1 + k # 向颜色索引表中添加 #6 - (1,1) 这条数据
               
                d_code_1 = d_code  

                # ------------- 通过解压过程生成颜色索引表，求得size ----------------end 
        return result



    def compressed_code_list(self):
        self.compressed, self.compressed_bin_list = self.lzw_compress(self.code_list, color_size=4, MiniCodeSize = 2)        
        self.decompressed = self.lzw_decompress(self.compressed, color_size=4)
        self.bin_to_code_list = self.lzw_bin_to_code(self.compressed_bin_list, color_size=4, MiniCodeSize = 2)
        return self

    
    def print(self):
        print("-----------------Uncompressed code list-----------------")
        # print(self.code_list)
        idx = 1
        for code in self.code_list:
            print(code, end=" ")
            if idx % 10 == 0:
                print()
            idx = idx + 1
        print("len", len(self.code_list))

        print("-----------------Compressed code list-----------------")
        print(self.compressed)
        print("len", len(self.compressed))
    
        print("-----------------Bin to code list-----------------")
        print(self.bin_to_code_list)
        print("len", len(self.bin_to_code_list))
    
        print("-----------------Compressed bin list-----------------")
        print([hex(bin) for bin in self.compressed_bin_list])
        print("len", len(self.compressed_bin_list))

        print("-----------------Decompressed code list-----------------")
        idx = 1
        for code in self.decompressed:
            print(code, end=" ")
            if idx % 10 == 0:
                print()
            idx = idx + 1
        print("len", len(self.decompressed))

        # print("-----------------Compressed dic size-----------------")
        # print(self.dictSize)
        # print("len", len(self.dictSize))

        # print("-----------------Code list to Byte list-----------------")
        # print(" ".join(f"{num:02x}" for num in self.byte_list))
        # print("len", len(self.byte_list))
        pass


CodeToByteList('gif_image_data.txt').load_code().compressed_code_list().print()